Skip to content

@ngtools/webpack: Enable the emit of declaration files#32763

Closed
lmartorella wants to merge 3 commits intoangular:mainfrom
iongroup:feat/dts
Closed

@ngtools/webpack: Enable the emit of declaration files#32763
lmartorella wants to merge 3 commits intoangular:mainfrom
iongroup:feat/dts

Conversation

@lmartorella
Copy link

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Currently the AngularWebpackPlugin is swallowing any .d.ts file. This differs from what the standard ts-loader plugin does: it normally produces a .d.ts file for each source .ts file, alongside the source itself.
If the tsconfig declarationDir property is used, the root folder of the declaration files can be changed instead (but it keeps the source folder structure).

Currently this is a viable workaround for Angular libraries:

            {
                test: /\.ts$/,
                use: [{
                    loader: "@ngtools/webpack",
                    options: {
                        // etc... sideEffects: true
                    }
                }, {
                    // Load `ts-loader` as well to allow .d.ts to be produced in the `declarationDir`: `@ngtools/webpack` is not doing that
                    loader: "ts-loader"
                }]
           }

However this produces declaration files that doesn't contains Ivy metadata, so it cannot be used by other Angular libraries.

Using the official ng-packagr to build Angular libraries leverages a different stack (rollup), so any webpack-based solution with custom plugins cannot be easily ported to ng-packagr.

This PR only shows the feasibility for producing the .d.ts again (with Ivy metadata), but it is not production ready.

Is there any reasons for not considering the declaration files emission for webpack plugin?

What is the new behavior?

New behavior aligns the angular webpack plugin to ts-loader, emitting the unbundled .d.ts files in place (or in the target declarationDir) with Ivy information, at par with the ones of ng-packagr.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds the capability for the Angular webpack plugin to emit TypeScript declaration files, which is a valuable feature. The changes to capture the .d.ts file content from the compiler are well-implemented. However, the method used to write these files to disk in packages/ngtools/webpack/src/ivy/loader.ts has significant issues. It uses synchronous file I/O which will block the event loop and degrade performance, and it doesn't ensure the target directory exists, which could cause crashes. I've left specific comments with suggestions for improvement. Addressing these points will be crucial for making this feature production-ready.

const relDir = path.relative(fileEmitter.compilerOptions.baseUrl, target);
target = path.join(fileEmitter.compilerOptions.declarationDir, relDir);
}
fs.writeFileSync(target, result.declaration);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This synchronous file write has two main problems:

  1. It's a blocking I/O call that will negatively impact performance, especially in watch mode.
  2. It will throw an error if the parent directory of target does not exist.

Ideally, this should be an asynchronous operation using fs.promises.writeFile after ensuring the directory exists with fs.promises.mkdir. This would require restructuring the promise handling in this loader to await the file I/O before calling the webpack callback.

As a minimal fix that addresses the potential crash, you could use fs.mkdirSync(path.dirname(target), { recursive: true }); before this line, but the performance issue with synchronous I/O would remain a significant concern.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously I expect a cached mechanism to avoid to emit the files immediately or before returning the async call. Please point to a possible usage of output-only caching support already part of the project, if one.


// Write the declaration file in the target dir
if (result.declaration && fileEmitter.compilerOptions.declaration) {
let target = this.resourcePath.replace('.ts', '.d.ts');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using String.prototype.replace() with a string argument is not robust. It only replaces the first occurrence and doesn't handle different TypeScript file extensions like .mts or .cts correctly. A regular expression should be used to safely replace the file extension and handle all variants.

Suggested change
let target = this.resourcePath.replace('.ts', '.d.ts');
let target = this.resourcePath.replace(/(\.[cm]?ts)$/, '.d$1');

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good bot

@alan-agius4
Copy link
Collaborator

Declaration emitting is disabled by design in @ngtools/webpack. This plugin is specifically optimized for application builds where type outputs aren't necessary.

For library builds, ng-packagr remains the only officially supported tool. Additionally, please keep in mind that the Webpack build pipeline is no longer receiving new features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants